#pragma once
#include "RenderCommon.h"

class COglProgram
{
public:
	COglProgram()
		: m_bLoaded(FALSE)
		, m_uiProgram(0)
		, m_uiVertexShader(0)
		, m_uiFragmentShader(0)
	{
	}

	virtual ~COglProgram()
	{
		Unload();
	}

	HRESULT LoadProgram(LPCSTR szVertexShader, LPCSTR szFragmentShader)
	{
		GLint nValue = 0;

		if(m_bLoaded)return HRESULT_FROM_WIN32(ERROR_INVALID_STATE);

		//compile vertex shader
		GLuint uiVertexShader = glCreateShader(GL_VERTEX_SHADER);
		OGL_ASSERT_OK();
		glShaderSource(uiVertexShader, 1, &szVertexShader, NULL);
		OGL_ASSERT_OK();
		glCompileShader(uiVertexShader);
		glGetShaderiv(uiVertexShader, GL_COMPILE_STATUS, &nValue);
		if(nValue != GL_TRUE)
		{
			glGetShaderiv(uiVertexShader, GL_INFO_LOG_LENGTH, &nValue);
			if(nValue > 0)
			{
				CHAR* szInfo = new CHAR[nValue + 1];
				glGetShaderInfoLog(uiVertexShader, nValue + 1, NULL, szInfo);
				::OutputDebugStringA(szInfo);
				delete[] szInfo;
			}

			glDeleteShader(uiVertexShader);

			return HRESULT_FROM_WIN32(ERROR_FILE_INVALID);
		}

		//compile fragment shader
		GLuint uiFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
		OGL_ASSERT_OK();
		glShaderSource(uiFragmentShader, 1, &szFragmentShader, NULL);
		OGL_ASSERT_OK();
		glCompileShader(uiFragmentShader);
		glGetShaderiv(uiFragmentShader, GL_COMPILE_STATUS, &nValue);
		if(nValue != GL_TRUE)
		{
			glGetShaderiv(uiFragmentShader, GL_INFO_LOG_LENGTH, &nValue);
			if(nValue > 0)
			{
				CHAR* szInfo = new CHAR[nValue + 1];
				glGetShaderInfoLog(uiFragmentShader, nValue + 1, NULL, szInfo);
				::OutputDebugStringA(szInfo);
				delete[] szInfo;
			}

			glDeleteShader(uiVertexShader);
			glDeleteShader(uiFragmentShader);

			return HRESULT_FROM_WIN32(ERROR_FILE_INVALID);
		}

		//attach shaders to program
		GLuint uiProgram = glCreateProgram();
		OGL_ASSERT_OK();
		glAttachShader(uiProgram, uiVertexShader);
		OGL_ASSERT_OK();
		glAttachShader(uiProgram, uiFragmentShader);
		OGL_ASSERT_OK();

		//link program
		glLinkProgram(uiProgram);
		glGetProgramiv(uiProgram, GL_LINK_STATUS, &nValue);
		if(nValue != GL_TRUE)
		{
			glGetProgramiv(uiProgram, GL_INFO_LOG_LENGTH, &nValue);
			if(nValue > 0)
			{
				CHAR* szInfo = new CHAR[nValue + 1];
				glGetProgramInfoLog(uiProgram, nValue + 1, NULL, szInfo);
				::OutputDebugStringA(szInfo);
				delete[] szInfo;
			}

			glDetachShader(uiProgram, uiVertexShader);
			glDetachShader(uiProgram, uiFragmentShader);
			glDeleteShader(uiVertexShader);
			glDeleteShader(uiFragmentShader);
			glDeleteProgram(uiProgram);
			return HRESULT_FROM_WIN32(ERROR_INTERNAL_ERROR);
		}

		m_uiProgram = uiProgram;
		m_uiVertexShader = uiVertexShader;
		m_uiFragmentShader = uiFragmentShader;
		m_bLoaded = TRUE;

		return S_OK;
	}

	HRESULT LoadProgramFromFile(LPCSTR szVertexShaderFile, LPCSTR szFragmentShaderFile)
	{
		if(m_bLoaded)return HRESULT_FROM_WIN32(ERROR_INVALID_STATE);

		exstring strVertexPath(szVertexShaderFile);
		exstring strFragmentPath(szFragmentShaderFile);

		if(szVertexShaderFile[1] != ':' || szFragmentShaderFile[1] != ':')
		{
			CHAR szModulePath[MAX_PATH];
			exstring strMoudlePath;
			size_t nSlashPos = 0;

			::GetModuleFileNameA(NULL, szModulePath, MAX_PATH);
			strMoudlePath = szModulePath;
			nSlashPos = strMoudlePath.find_last_of('\\');
			strMoudlePath = strMoudlePath.left(nSlashPos + 1);

			if(szVertexShaderFile[1] != ':')
			{
				strVertexPath = strMoudlePath + strVertexPath;
			}
			if(szFragmentShaderFile[1] != ':')
			{
				strFragmentPath = strMoudlePath + strFragmentPath;
			}
		}

		LPSTR szVertexShader = ReadShaderFile(strVertexPath);
		LPSTR szFragmentShader = ReadShaderFile(strFragmentPath);

		if(!szVertexShader || !szFragmentShader)
		{
			if(szVertexShader)delete[] szVertexShader;
			if(szFragmentShader)delete[] szFragmentShader;
			return HRESULT_FROM_WIN32(ERROR_FILE_INVALID);
		}

		HRESULT hr = LoadProgram(szVertexShader, szFragmentShader);

		delete[] szVertexShader;
		delete[] szFragmentShader;

		return hr;
	}

	void Unload()
	{
		if(!m_bLoaded)return;

		if(m_uiProgram)
		{
			//detach shaders from program
			glDetachShader(m_uiProgram, m_uiVertexShader);
			glDetachShader(m_uiProgram, m_uiFragmentShader);

			//delete shaders and program
			glDeleteShader(m_uiVertexShader);
			m_uiVertexShader = 0;
			glDeleteShader(m_uiFragmentShader);
			m_uiFragmentShader = 0;
			glDeleteProgram(m_uiProgram);
			m_uiProgram = 0;
		}

		m_bLoaded = FALSE;
	}

	operator GLuint()
	{
		return m_uiProgram;
	}

private:
	BOOL m_bLoaded;
	GLuint m_uiProgram;
	GLuint m_uiVertexShader;
	GLuint m_uiFragmentShader;
	LPSTR ReadShaderFile(LPCSTR szShaderFile)
	{
		FILE* pFile = NULL;
		LONG lFileLength = 0;
		CHAR* pBuffer = NULL;

		fopen_s(&pFile, szShaderFile, "r");
		if(NULL != pFile)
		{
			fseek(pFile, 0, SEEK_END);
			lFileLength = ftell(pFile);
			rewind(pFile);

			pBuffer = new CHAR[lFileLength + 1];
			::ZeroMemory(pBuffer, lFileLength + 1);
			fread(pBuffer, 1, lFileLength, pFile);
		}

		return pBuffer;
	}
};